技巧24 使用ADD指令实现智能的缓存清除

在技巧23里,用户了解了如何在选择的时间点执行中间构建的时候清除缓存,这本身跟使用 –-no-cache 标志完全忽略缓存的作用是相同水平的。

现在,用户可以把它提升到一个新的高度,这样用户就可以在必要时自动清除缓存。这样可以节省用户大量的时间和计算,也就是可以省钱!

问题

想要在一个远端资源更改时清除缓存。

解决方案

使用Dockerfile的 ADD 指令仅在URL响应发生更改时清除缓存。

对Dockerfile的早期批评之一便是它们所谓可以产出可靠的构建结果的说辞是具有误导性的。实际上,我们早在2013年就和Docker的创始人讨论过这个主题。

具体来说,如果像如下这样使用Dockerfile里的指令发起网络调用:

RUN git clone https://github.com/nodejs/node

在默认情况下,每个Docker守护进程每次执行Docker构建时都会执行一次。GitHub上的代码可能会发生很大变化,但是就Docker守护进程而言,构建往往都是最新的。随着时间的流逝,同一个Docker守护进程仍然会使用缓存。

这可能听起来像是一个理论问题,但是对于许多用户来说,这是一个非常实际的问题。我们已经在工作中多次看到这样的事情发生,并因此造成混乱。用户可能之前了解过一些解决方案,但是对于许多复杂或大型的构建来说,这些解决方案并不够精细。

1.智能清除缓存的模式

试想用户有一个看上去像如代码清单4-8所示的Dockerfile。(注意,它只是一个展示原理的Dockerfile模式,它并不能工作。)

代码清单4-8 一个示例Dockerfile

FROM ubuntu:16.04
RUN apt-get install -y git(以及很多其他软件包)  ⇽--- 作为先决条件,安装一系列的软件包
RUN git clone https://github.com/nodejs/node  ⇽--- 克隆一个经常变动的仓库(以nodejs为例)
WORKDIR node
RUN make && make install  ⇽--- 执行make和install命令,构建项目

这份Dockerfile为创建高效的构建过程带来了一些挑战。如果想要每次都从头开始构建所有东西,解决方案很简单:使用 –-no-cache 参数进行 dockerbuild 。这样做的问题是,每次运行构建时,都会在第二行重复进行软件包的安装,这是(在大部分情况下)没必要的。

这个问题可以通过在 gitclone 之前清除缓存来解决(正如技巧23演示的那样)。然而,这又带来了另外一个挑战:如果Git仓库没有更改怎么办?然后,用户正在进行一个可能成本很高的网络传输,随后便是可能很耗时的 make 命令。网络、计算和磁盘资源都在不必要地使用。

解决这个问题的一种办法是使用技巧23,每当用户知道远端仓库更改时,使用一个新的值传递构建参数。但是这仍然需要人工介入来确认是否有变化并且做出一些干预。

用户需要的是一条命令,它可以确定自上次构建以来资源是否已经更改,然后只有当有更改时才清除缓存。

2.ADD指令——意想不到的好处

输入 ADD 指令!

用户之前已经熟悉了 ADD ,它是Dockerfile的一条基础指令。通常用于在生成的镜像里添加文件,但是 ADD 有两个很有用的功能,用户可以因地制宜,在自己的上下文里使用它:它会缓存它所引用的文件的内容,并且可以指定网络资源作为参数。这意味着只要Web请求的输出发生更改,缓存便会被清除。

克隆代码仓库时如何利用这一点呢?其实,这取决于用户在网络上引用的资源的本身属性。许多资源会维护一个页面,当代码仓库自身更改时它也会更新,但是这些页面会因为资源类型的不同而有所差异。在这里,我们将专注于GitHub仓库,因为这是一个常见的用例。

GitHub API提供了一个有用的资源,可以在这里提供帮助。它会针对每个仓库维护一组URL,它们会以JSON格式返回最近提交的 commit 。当有新的提交时,该URL响应的内容也会随之发生变化,如代码清单4-9所示。

代码清单4-9 使用ADD触发清除缓存

FROM ubuntu:16.04
ADD https://api.github.com/repos/nodejs/node/commits  ⇽--- 提交新的commit时返回内容也会随之更改的URL/dev/null  ⇽--- 我们并不关心它的输出内容存放的文件位置,因此我们将它发送到/dev/null
RUN git clone https://github.com/nodejs/node  ⇽--- 只有存在更改时才会进行git clone
[...]

上述做法的结果便是只有当从上次构建以来对仓库做过提交时才会清除缓存。无须人工干预,也无须手动检查。

如果用户想要使用更改更加频繁的仓库来测试这个机制,不妨尝试使用Linux内核,如代码清单4-10所示。

代码清单4-10 将Linux内核代码添加到镜像里

FROM ubuntu:16.04
ADD https://api.github.com/repos/torvalds/linux/commits/dev/null  ⇽---  ADD命令,这次使用的是Linux仓库
RUN echo "Built at: $(date)" >> /build_time  ⇽--- 将系统日期输出到构建的镜像里,这样我们便能得知上次清除缓存构建发生的时间

如果用户创建一个文件夹并将前面的代码放到Dockerfile里,然后定期执行代码清单4-11所示的命令(如一小时一次),那么输出日期将仅在Linux的Git仓库变动时修改。

代码清单4-11 构建Linux代码镜像

$ docker build -t linux_last_updated .   ⇽--- 构建镜像并把它命名为linux_last_updated
$ docker run linux_last_updated cat /build_time  ⇽--- 从结果镜像里输出/build_time文件的内容

讨论

本技巧展示了一种有价值的自动化技术,它可以确保仅在必要时做构建。

本技巧还讲解了 ADD 命令如何工作的一些具体细节。用户看到的“文件”可能是一份网络资源,如果文件(或网络资源)自上一次构建以来变更过,它将会把之前的缓存清除。

此外,用户还可以看到网络资源拥有相关的资源,可以指示用户引用的资源是否改动过。举个例子,尽管用户可以通过引用主要的GitHub页面来查看其中是否发生过任何更改,但是页面上的更改可能比上次提交更频繁(比如,Web响应的时间隐藏在页面资源里,或者每个响应都有唯一的引用字符串)。

针对GitHub的情况,正如所见,你可以引用它提供的API。其他服务,如BitBucket,提供类似的资源。如果用户正在构建基于Kubernetes的项目,那么也许可以在Dockerfile里添加一行 ADD 命令,以便在其响应发生变化时清除缓存。

results matching ""

    No results matching ""